package com.dl.redis.util;

import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author DL
 * @create 2023-09-12 17:21
 */
@Component
public class RedisLockUtil {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Value("${redis.lockKey:my_lock_key}")
    private String lockKey;
    private final String lockValue = UUID.randomUUID().toString();
    @Value("${redis.maxRetries:20}")
    private long maxRetries;
    @Value("${redis.retryIntervalMillis:500}")
    private long retryIntervalMillis;
    @Value("${redis.lockTtl:5}")
    private long lockTtl;

    private static final String LOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "   return redis.call('del', KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";

    private static final String UNLOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "   return redis.call('del', KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";

    /**
     * redis分布式锁加锁方法
     * @return boolean
     * @author DL
     * @date 2023/09/14
     */
    public boolean lock() {
        return lock(lockKey, lockValue);
    }

    /**
     * redis分布式锁加锁方法
     * @param key 分布式锁的键
     * @param value 分布式锁的值
     * @return boolean
     * @author DL
     * @date 2023/09/14
     */
    public boolean lock(String key, String value) {
        long retries = 0;
        while (retries < maxRetries) {
            Boolean locked = redisTemplate.opsForValue().setIfAbsent(key, value, lockTtl, TimeUnit.SECONDS);
            if (locked != null && locked) {
                // 成功获取锁，执行业务逻辑
                LockContext.setLockUuid(value);
                return true;
            } else {
                // 获取锁失败，等待一段时间后重试
                retries++;
                System.out.println("重试次数：" + retries);
                try {
                    TimeUnit.MILLISECONDS.sleep(retryIntervalMillis);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        // 达到最大重试次数，放弃获取锁
        return false;
    }

    /**
     * redis分布式锁加锁方法，lua脚本版
     * @return boolean
     * @author DL
     * @date 2023/09/14
     */
    public boolean lockLua() {
        return lockLua(lockKey, lockValue);
    }

    /**
     * redis分布式锁加锁方法，lua脚本版
     * @param key 分布式锁的键
     * @param value 分布式锁的值
     * @return boolean
     * @author DL
     * @date 2023/09/14
     */
    public boolean lockLua(String key, String value) {
        RedisScript<Long> lockRedisScript = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class);
        Long result = redisTemplate.execute(lockRedisScript, Collections.singletonList(key), value);
        if ( result != null && result == 1) {
            LockContext.setLockUuid(value);
            return true;
        }
        return false;
    }

    /**
     * redis分布式锁解锁方法
     * @author DL
     * @date 2023/09/14
     */
    public void unlock() {
        unlock(lockKey);
    }

    /**
     * redis分布式锁解锁方法
     * @param key 分布式锁的键
     * @author DL
     * @date 2023/09/14
     */
    public void unlock(String key) {
        String uuid = LockContext.getLockUuid();
        if (null != uuid && !"".equals(uuid)) {
            RedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
            Long result = redisTemplate.execute(script, Collections.singletonList(key), uuid);
            if (result != null && result == 1) {
                LockContext.clearLockUuid(); // 清除ThreadLocal中的UUID
            }
        }else {
            throw new RuntimeException("当前线程没有获取到分布式锁");
        }
    }

    /**
     * 分布式锁删除方法
     * @author DL
     * @date 2023/09/14
     */
    public void deleteLock() {
        deleteLock(lockKey);
    }

    /**
     * 分布式锁删除方法
     * @param key 分布式锁的键
     * @author DL
     * @date 2023/09/14
     */
    public void deleteLock(String key) {
        // 清除ThreadLocal中的UUID
        LockContext.clearLockUuid();
        redisTemplate.delete(key);
    }

}
